序、OpenGL曲面细分
关于Tessallation
Tessallation(镶嵌)在图形学通常是指曲面细分。
Patch:
Patch是曲面细分着色器使用的一种新的图元类型 (
GL_PATCHES
)。Patch仅仅是传入到OpenGL的一组顶点(控制点)。
使用一个patch时,需要告诉OpenGL顶点数组中要使用多少个顶点来组成一个patch,而这可以通过使用
glPatchParameteri()
进行指定。
OpenGL对曲面细分的支持通过三个管线阶段提供:
- 曲面细分控制着色器。(可编程)(指定曲面细分器要生成的网格拓扑结构)
- 曲面细分器。
- 曲面细分评估着色器。 (可编程)(允许以各种方式操纵网格)
曲面细分过程中数据在管线的传递
顶点着色器:
=>
position
texcoord
...
gl_Position
texcoord
...
=>
- 补充:
- 顶点着色器操纵的是控制点。把实际生成顶点的相关处理(例如MVP变换)放到TES中。
- 可以在顶点着色器中用gl_VertexID硬编码控制点,也可以用缓冲区传入。
曲面细分控制着色器(TCS):
=>
gl_in[gl_InvocationID].gl_Position
texcoord[gl_InvocationID]
...
gl_out[gl_InvocationID].gl_Position
texcoord_tcs_out[gl_InvocationID]
...
=>
- 补充:
- 用
gl_TessLevelOuter/Inner[]
指定内外细分层级。 - 可以计算细节级别(LOD)。
- 用
曲面细分计算着色器(TES):
=>
gl_in[gl_InvocationID].gl_Position
texcoord_tcs_out[gl_InvocationID]
...
gl_Position
texcoord_tes_out
...
=>
- 补充:
- 用布局说明符
layout (......) in;
指定细分选项。 - 在TES中可以使用
gl_TessCoord
获取网格点位置。这个位置具有两个或三个分量,依赖于patch的类型。(只有在TES中可用)(范围:[0, 1]) - 在TES进行实际生成顶点的相关处理,例如MVP变换等。
- 用布局说明符
实现示例一、16控制点贝塞尔曲面
App:
float vertices[16 * 3] =
{
-1.0f, 0.5f, -1.0f, -0.5f, 0.5f, -1.0f, 0.5f, 0.5f, -1.0f, 1.0f, 0.5f, -1.0f,
-1.0f, 0.0f, -0.5f, -0.5f, 0.0f, -0.5f, 0.5f, 0.0f, -0.5f, 1.0f, 0.0f, -0.5f,
-1.0f, 0.0f, 0.5f, -0.5f, 0.0f, 0.5f, 0.5f, 0.0f, 0.5f, 1.0f, 0.0f, 0.5f,
-1.0f,-0.5f, 1.0f, -0.5f, 0.3f, 1.0f, 0.5f, 0.3f, 1.0f, 1.0f, 0.3f, 1.0f
};
glGenVertexArrays(numVAOs, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, 16 * 3 * 4, &vertices[0], GL_STATIC_DRAW);
//...
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glPatchParameteri(GL_PATCH_VERTICES, 16);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDrawArrays(GL_PATCHES, 0, 16);
顶点着色器:
#version 430
out vec2 texcoord;
layout (location = 0) in vec3 position;
void main(void)
{
texcoord = vec2((position.x + 1.0)/2.0, (position.z + 1.0)/2.0);//为当前顶点计算纹理坐标
gl_Position = vec4(position, 1.0); //原始顶点。TES阶段再MVP变换
}
TCS:
#version 430
in vec2 texcoord[];
out vec2 texcoord_tcs_out[];
layout (vertices = 16) out;
void main(void)
{ int TL = 6; // 曲面细分等级(内外都是32)
if (gl_InvocationID ==0)
{ gl_TessLevelOuter[0] = TL;
gl_TessLevelOuter[2] = TL;
gl_TessLevelOuter[1] = TL;
gl_TessLevelOuter[3] = TL;
gl_TessLevelInner[0] = TL;
gl_TessLevelInner[1] = TL;
}
texcoord_tcs_out[gl_InvocationID] = texcoord[gl_InvocationID];//纹理坐标传递
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;//传递gl_Position属性
}
//gl_InvocationID--整型id计数器,指示TCS正在执行哪个调用,常用于传递顶点属性比如gl_Position。
//gl_in[]--传入的控制点数组。每个控制点是一个数组元素,可以访问其顶点属性。
//gl_out[]--输出给TES的控制点数组。用法同上。
TES:
#version 430
layout (quads, equal_spacing,ccw) in;//细分选项:四边形,等距细分、顶点逆时针缠绕。
//补充:如果我们想输出单个点,而不是等值线或填充区域的话,我们可以应用point_mode选项。
uniform mat4 mvp_matrix;
in vec2 texcoord_tcs_out[];
out vec2 texcoord_tes_out;
void main (void)
{ vec3 p00 = (gl_in[0].gl_Position).xyz;
vec3 p10 = (gl_in[1].gl_Position).xyz;
vec3 p20 = (gl_in[2].gl_Position).xyz;
vec3 p30 = (gl_in[3].gl_Position).xyz;
vec3 p01 = (gl_in[4].gl_Position).xyz;
vec3 p11 = (gl_in[5].gl_Position).xyz;
vec3 p21 = (gl_in[6].gl_Position).xyz;
vec3 p31 = (gl_in[7].gl_Position).xyz;
vec3 p02 = (gl_in[8].gl_Position).xyz;
vec3 p12 = (gl_in[9].gl_Position).xyz;
vec3 p22 = (gl_in[10].gl_Position).xyz;
vec3 p32 = (gl_in[11].gl_Position).xyz;
vec3 p03 = (gl_in[12].gl_Position).xyz;
vec3 p13 = (gl_in[13].gl_Position).xyz;
vec3 p23 = (gl_in[14].gl_Position).xyz;
vec3 p33 = (gl_in[15].gl_Position).xyz;
// gl_TessCoord保存的是细分网格点位置。这个位置具有两个或三个分量,依赖于patch的类型。
//(只有在TES中可用)(范围:[0, 1])
float u = gl_TessCoord.x;
float v = gl_TessCoord.y;
// 三次贝塞尔基础函数
float bu0 = (1.0-u) * (1.0-u) * (1.0-u); //(1-u)^3
float bu1 = 3.0 * u * (1.0-u) * (1.0-u); //3u(1-u)^2
float bu2 = 3. * u * u * (1.0-u); //3u^2(1-u)
float bu3 = u * u * u; //u^3
float bv0 = (1.0-v) * (1.0-v) * (1.0-v); //(1-v)^3
float bv1 = 3.0 * v * (1.0-v) * (1.0-v); //3v(1-v)^2
float bv2 = 3. * v * v * (1.0-v); //3v^2(1-v)
float bv3 = v * v * v; //v^3
// 输出细分后的顶点位置
vec3 outputPosition =
bu0 * ( bv0*p00 + bv1*p01 + bv2*p02 + bv3*p03 )
+ bu1 * ( bv0*p10 + bv1*p11 + bv2*p12 + bv3*p13 )
+ bu2 * ( bv0*p20 + bv1*p21 + bv2*p22 + bv3*p23 )
+ bu3 * ( bv0*p30 + bv1*p31 + bv2*p32 + bv3*p33 );
//最终位置
gl_Position = mvp_matrix * vec4(outputPosition,1.0f);
// 输出插值后的纹理坐标
vec2 tc1 = mix(texcoord_tcs_out[0], texcoord_tcs_out[3], gl_TessCoord.x);
vec2 tc2 = mix(texcoord_tcs_out[12], texcoord_tcs_out[15], gl_TessCoord.x);
vec2 tc = mix(tc2, tc1, gl_TessCoord.y);
texcoord_tes_out = tc;
}
片段着色器:
#version 430
in vec2 texcoord_tes_out;
out vec4 color;
uniform mat4 mvp_matrix;
layout (binding=0) uniform sampler2D tex_color;
void main(void)
{
color = texture(tex_color, texcoord_tes_out);
}
实现示例二、地形细分
说明:
- 使用了LOD技术,显著降低了系统负载。
- 使用了Instance机制,因为曲面细分值最大64x64,不足以满足地形精细度需求。
补充:
- 可以采样法线贴图来实现地形的光照效果。
- 可以通过把摄像机后面的Patch外部和内部层级设置为0来实现剔除技术。
代码:
App:
//......
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvpMat));
//......
glPatchParameteri(GL_PATCH_VERTICES, 4);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDrawArraysInstanced(GL_PATCHES, 0, 4, 64*64);
//......
Vert Shader:
#version 430
out vec2 texcoord;
uniform mat4 mvp_matrix;
uniform float length_side;//地形边长
layout (binding = 0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;
void main(void)
{
// 基于InstanceID指定偏移
int x = gl_InstanceID % 64;
int y = gl_InstanceID / 64;
// 纹理坐标分配进64个Patch实例。(归一化为[0, 1],翻转Y坐标)
const vec2 patchTexCoords[] = vec2[](vec2(0,0), vec2(1,0), vec2(0,1), vec2(1,1));
texcoord = vec2( (x + patchTexCoords[gl_VertexID].x) / 64.0,
(63 - y + patchTexCoords[gl_VertexID].y) / 64.0);
// 顶点位置 (从-0.5边长到+0.5边长)
gl_Position = vec4((texcoord.x - 0.5) * length_side, 0.0, ((1.0 - texcoord.y) - 0.5) * length_side, 1.0);
}
TCS:
#version 430
layout (vertices = 4) out;
in vec2 texcoord[];
out vec2 texcoord_tcs_out[];
uniform mat4 mvp_matrix;
layout (binding=0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;
void main(void)
{ //设置LOD级别
float subdivisions = 16.0;//可调整常量
if (gl_InvocationID == 0)
{ vec4 p0 = mvp_matrix * gl_in[0].gl_Position; p0 = p0 / p0.w;
vec4 p1 = mvp_matrix * gl_in[1].gl_Position; p1 = p1 / p1.w;
vec4 p2 = mvp_matrix * gl_in[2].gl_Position; p2 = p2 / p2.w;
float width = length(p1-p0) * subdivisions + 1.0;
float height = length(p2-p0) * subdivisions + 1.0;
gl_TessLevelOuter[0] = height;
gl_TessLevelOuter[1] = width;
gl_TessLevelOuter[2] = height;
gl_TessLevelOuter[3] = width;
gl_TessLevelInner[0] = width;
gl_TessLevelInner[1] = height;
}
//传递位置和纹理坐标
texcoord_tcs_out[gl_InvocationID] = texcoord[gl_InvocationID];
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
}
TES:
#version 430
layout (quads, fractional_even_spacing, ccw) in;//等间距改为分数间距。(避免LOD突变)
//layout (quads, equal_spacing, ccw) in;
uniform mat4 mvp_matrix;
uniform float length_side;
layout (binding = 0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;
in vec2 texcoord_tcs_out[];
out vec2 texcoord_tes_out;
void main (void)
{ //子网格顶点纹理坐标
vec2 texcoord = vec2(texcoord_tcs_out[0].x + (gl_TessCoord.x) / 64.0,
texcoord_tcs_out[0].y + (1.0 - gl_TessCoord.y) / 64.0);
// 子网格顶点位置
vec4 tessellatedPoint = vec4(gl_in[0].gl_Position.x + gl_TessCoord.x * length_side / 64.0, 0.0,
gl_in[0].gl_Position.z + gl_TessCoord.y * length_side / 64.0, 1.0);
// 采样高度的高度值增加给顶点
tessellatedPoint.y += (texture(tex_height, texcoord).r) / 60.0;
//输出顶点位置和纹理坐标准备插值
gl_Position = mvp_matrix * tessellatedPoint;
texcoord_tes_out = texcoord;
}
Frag Shader:
#version 430
in vec2 texcoord_tes_out;
out vec4 color;
uniform mat4 mvp_matrix;
layout (binding = 0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;
void main(void)
{ color = texture(tex_color, texcoord_tes_out);
}